//autor: Juan Manuel Fuentes Diaz; 46713095A
//Esta clase implementa un diccionario, con un vector de palabras, 
//una lista de letras del alfabeto (a,b,c,..y,z), un vector de Nodos para cada una de estas letras
// que operar?n con los m?todos de un arbol AVL y un entero que es la cantidad total de palabras 
package Dominio;

import java.io.*;
import java.util.*;

public class DiccionarioPalabras implements Serializable{

    private int totpalabras;
    private Vector<Palabra> pal;
    private AVL avlvec;
    private Nodo[] nodo;
    private String[] alfabet = {"a", "�", "�", "b", "c", "�", "d", "e", "�", "�", "f", "g",
        "h", "i", "�", "�", "j", "k", "l", "m", "�", "n", "o", "�", "�",
        "p", "q", "r", "s", "t", "u", "�", "�", "v", "w", "x", "y", "z"
    };

    public void crearDiccionarioVacio() {
        //Pre:-
        //Post: Crea un diccionario vacio, con primera posici?n del vector pal igual a null
        this.totpalabras = 0;
        this.pal = new Vector<Palabra>(0, 1);
        this.pal.add(null);
        this.avlvec = new AVL();
        this.nodo = new Nodo[38];
    }

    public boolean existLetra(char c) {
        //Pre: c es un char no vacio
        //Post: devuelve cierto si c est? en el alfabeto de diccionario
        boolean bool = false;
        char[] sc = {c};
        char l = ((new String(sc)).toLowerCase()).charAt(0);
        for (int i = 0; i < this.alfabet.length && bool == false; ++i) {
            bool = this.alfabet[i].charAt(0)== l;
        }
        return bool;
    }
    private int letraAlfabeto(char c){
        //Pre: c es un char no vacio
        //Post: devuelve la posicion del vector de nodos donde las palabras empiezan por la letra c
        int i;
        for (i = 0; i < this.alfabet.length && !(this.alfabet[i].charAt(0) == c); ++i) {
        }
        return i;
    }

    public int existPalabra(String palabra) {
        //Pre: La primera letra de palabra ha de existir en el alfabeto de diccionario, si esto no se
        //cumple lanzar? excepciones de tipo Array y diccionario est� creado)
        //Post: devuelve cierto si existe la Palabra cuyo string es palabra
        palabra = palabra.toLowerCase();
        int i = letraAlfabeto(palabra.charAt(0));
        if (this.nodo[i] == null) {
            return 0;
        } else {
            return this.avlvec.buscar(this.nodo[i], palabra);
        }
    }

    public int getTotalPalabras() {
        //Pre: -
        //Post: devuelve el total de palabras del diccionario
        return this.totpalabras;
    }

    public int getTotPalAvl(char c) {
        //Pre: c es una char no nulo
        //Post: devuelve el total de palabras de diccionario que empicen por la letra c
        char[] sc = {c};
        char l = ((new String(sc)).toLowerCase()).charAt(0);
        int i = letraAlfabeto(l);
        return this.nodo[i].getTotElem();
    }

    public boolean palabraActivada(String palabra)  {
        //Pre: La primera letra de palabra ha de existir en el alfabeto de diccionario (Si esto no se cumple 
        //     lanzar? excepciones de tipo Array), y Palabra cuyo string es palabra, existe en diccionario.  
        //Post: Devuelve cierto si el booleano estaMarcado de Palabra es true
        palabra = palabra.toLowerCase();
        int i = letraAlfabeto(palabra.charAt(0));
        int x = this.avlvec.buscar(this.nodo[i], palabra);
        return this.pal.elementAt(x).estaMarcado();
    }

    public Palabra consultarPalabra(int id)  {
        //Pre: id es un n?mero v?lido en el vector de palabras, si id = 0,se lanzar? una excepci?n
        //     nullpointer, si id<0 ? id>pal.size() lanzar? la excepci?n de ArrayIndexOutOfBoundsException
        //Post: devuelve la palabra con id igual al argumento de entrada
        return this.pal.get(id);
    }

    public boolean addPalabra(Palabra palabra) {
        //Pre: Primera letra del string de Palabra existe en el alfabeto de diccionario 
        //     y Palabra no existe en diccionario
        //Post: devuelve cierto si se ha a?adido satisfactoriamente Palabra
        palabra.setValor(palabra.getValor().toLowerCase());
        int i = letraAlfabeto(palabra.getValor().charAt(0));
        if (this.nodo[i] == null) {
            this.nodo[i] = new Nodo();
        }
        if (this.avlvec.insertar(this.nodo[i], palabra)) {
            this.pal.add(palabra);
            palabra.setId(this.pal.size() - 1);
            this.totpalabras++;
            return true;
        } else {
            return false;
        }

    }

    /*public void estructura() {
        //llama a la estructura de los arboles avls, este metodo es para hacer pruebas e ir? fuera
        int i;
        for (i = 0; i < this.alfabet.length; i++) {
            if (this.nodo[i] == null) {
                this.nodo[i] = new Nodo();
            }
            estructura(this.nodo[i]);
        }

    }/*
    /*private void estructura(Nodo nodo) {  
            //escribe la estructura AVL, el primer parentesis es la altura
            //el segundo parentesis es el numero de hijs mas el padre
            //este metodo es para hacer pruebas y ver el correcto balanceo del avl e ir? fuera de ?sta clase
            if (nodo.exists()) {                                       
                if (nodo.getHdcha().exists() && nodo.getHizda().exists()) {
                    System.out.print(nodo.getPalabra().getValor() + "(" + nodo.getAltura() + ")(" + nodo.getTotElem() + ")->");
                    System.out.print("hijo izquierdo: " + nodo.getHizda().getPalabra().getValor() + "(" + nodo.getHizda().getAltura() + ")(" + nodo.getHizda().getTotElem() + "), ");
                    System.out.print("hijo derecho: " + nodo.getHdcha().getPalabra().getValor() + "(" + nodo.getHdcha().getAltura() + ")(" + nodo.getHdcha().getTotElem() + ")\n");
                } else if (!nodo.getHdcha().exists() && nodo.getHizda().exists()) {
                    System.out.print(nodo.getPalabra().getValor() + "(" + nodo.getAltura() + ")(" + nodo.getTotElem() + ") ->");
                    System.out.print("hijo izquierdo: " + nodo.getHizda().getPalabra().getValor() + "(" + nodo.getHizda().getAltura() + ")(" + nodo.getHizda().getTotElem() + ")\n ");
                } else if (nodo.getHdcha().exists() && !nodo.getHizda().exists()) {
                    System.out.print(nodo.getPalabra().getValor() + "(" + nodo.getAltura() + ")(" + nodo.getTotElem() + ") ->");
                    System.out.print("hijo derecho: " + nodo.getHdcha().getPalabra().getValor() + "(" + nodo.getHdcha().getAltura() + ")(" + nodo.getHdcha().getTotElem() + ")\n");
                }
                estructura(nodo.getHizda());
                estructura(nodo.getHdcha());
            }


        }*/

    private class Nodo implements Serializable{

        private Palabra pal;
        private int altura;
        private Nodo hdcha;
        private Nodo hizda;
        private int n; //elementos del arbol

        public void setPalabra(Palabra pal) {
            //Pre: -
            //Post: asigna Palabra pal a la variable de clase pal
            this.pal = pal;
        }

        public Palabra getPalabra() {
            //Pre: -
            //Post: -
            return this.pal;
        }

        public void setAltura(int a) {
            //Pre:-
            //Post: asigna el entero a a la variable de clase altura
            this.altura = a;
        }

        public int getAltura() {
            //Pre:-
            //Post:-
            return this.altura;
        }

        public void setHdcha(Nodo hdcha) {
            //Pre:-
            //Post: asigna Nodo hdcha a la variable de clase hdcha
            this.hdcha = hdcha;
        }

        public Nodo getHdcha() {
            //Pre: -
            //Post:-
            return this.hdcha;
        }

        public void setHizda(Nodo hizda) {
            //Pre:-
            //Post: asigna Nodo hizda a la variable de clase hizda
            this.hizda = hizda;
        }

        public Nodo getHizda() {
            //Pre: -
            //Post:-
            return this.hizda;
        }

        public int getTotElem() {
            //Pre:-
            //Post:-
            return this.n;
        }

        public void setTotElem(int n) {
            //Pre:-
            //Post: asigna el entero n a la variable de clase n
            this.n = n;
        }

        public void crearNodo(Palabra pal) {
            //Pre:-
            //Post: Crea un nodo con pal = Palabra pal
            this.pal = pal;
            altura = 0;
            hdcha = new Nodo();
            hizda = new Nodo();
            n = 1;
        }

        public boolean exists() {
            //Pre: -
            //Post: -
            return (this.pal != null);
        }

        public void copia(Nodo q) {
            //Pre:-
            //Post: se crea un copia parcial del nodo 
            q.pal = this.pal;
            q.altura = 0;
            q.hdcha = this.hdcha;
            q.hizda = this.hizda;
            q.n = 1;
        }
    }

    private class AVL implements Serializable{

        private void actualizarAltura(Nodo p) {
            //Pre:-
            //Post: actualiza las alturas de Nodo p
            p.setAltura(1 + max(altura(p.getHizda()), altura(p.getHdcha())));
        }

        private int max(int x, int y) {
            //Pre:-
            //Post:-
            return x >= y ? x : y;
        }

        private int altura(Nodo p) {
            //Pre:-
            //Post:-
            return p.exists() ? p.getAltura() : -1;
        }

        private void actualizarTotElem(Nodo p) {
            //Pre:-
            //Post: actualiza el numero total de nodos que forman el arbol con raiz el nodo p
            p.setTotElem(1 + totElem(p.getHizda()) + totElem(p.getHdcha()));
        }

        private int totElem(Nodo p) {
            //Pre:-
            //Post:-
            return p.exists() ? p.getTotElem() : 0;
        }

        private void LL(Nodo p) {
            //Pre:-
            //Post: hace un movimiento simple de izquierda con el nodo raiz p
            Nodo q = new Nodo();
            p.copia(q);
            p.getHizda().copia(p);
            q.setHizda(p.getHdcha());
            p.setHdcha(q);
            actualizarAltura(q);
            actualizarTotElem(q);
            actualizarAltura(p);
            actualizarTotElem(p);
        }

        private void RR(Nodo p) {
            //Pre:-
            //Post: hace un movimiento simple de derecha con el nodo raiz p
            Nodo q = new Nodo();
            p.copia(q);
            p.getHdcha().copia(p);
            q.setHdcha(p.getHizda());
            p.setHizda(q);
            actualizarAltura(q);
            actualizarTotElem(q);
            actualizarAltura(p);
            actualizarTotElem(p);
        }

        private void LR(Nodo p) {
            //Pre:-
            //Post: hace un movimiento doble de izquierda-derecha con el nodo raiz p
            RR(p.getHizda());
            LL(p);
        }

        private void RL(Nodo p) {
            //Pre:-
            //Post: hace un movimiento doble de derecha-izquierda con el nodo raiz p
            LL(p.getHdcha());
            RR(p);
        }

        public boolean insertar(Nodo p, Palabra pala) { 
            //Pre:-
            //Post:inserta Palabra pala en el arbol con raiz Nodo p, devuelve cierto si se inserta satisfactoriamente
            if (p.exists()) {
                if (pala.getValor().compareTo(p.getPalabra().getValor()) < 0) {
                    insertar(p.getHizda(), pala);
                    if (altura(p.getHizda()) - altura(p.getHdcha()) >= 2) {
                        if (pala.getValor().compareTo(p.getHizda().getPalabra().getValor()) < 0) {
                            LL(p);
                        } else {
                            LR(p);
                        }
                    }
                    if (p.getHdcha().exists()) {
                        actualizarAltura(p.getHdcha());
                        actualizarTotElem(p.getHdcha());
                    }
                    actualizarAltura(p);
                    actualizarTotElem(p);
                } else if (pala.getValor().compareTo(p.getPalabra().getValor()) > 0) {
                    insertar(p.getHdcha(), pala);
                    if (altura(p.getHdcha()) - altura(p.getHizda()) >= 2) {
                        if (pala.getValor().compareTo(p.getHdcha().getPalabra().getValor()) > 0) {
                            RR(p);
                        } else {
                            RL(p);
                        }
                    }
                    if (p.getHizda().exists()) {
                        actualizarAltura(p.getHizda());
                        actualizarTotElem(p.getHizda());
                    }
                    actualizarAltura(p);
                    actualizarTotElem(p);
                } else {
                    return false;
                }
            } else {
                p.crearNodo(pala);
            }
            return true;
        }

        public int buscar(Nodo p, String pala) {
            //Pre:-
            //Post: devuelve el id de Palabra cuyo string es pala, sino est? devuelve 0
            if (p.exists()) {
                if (pala.compareTo(p.getPalabra().getValor()) < 0) {
                    return buscar(p.getHizda(), pala);
                } else if (pala.compareTo(p.getPalabra().getValor()) > 0) {
                    return buscar(p.getHdcha(), pala);
                } else {
                    return p.getPalabra().getId();
                }
            } else {
                return 0;
            }
        }
}
}
